#include "models/renderer.h"
#include "helpers/exception_handler.h"
#include "helpers/general.h"
#include "helpers/resource.h"
#include "models/canvas_renderer.h"
#include <js_native_api.h>

using namespace std::chrono_literals;

namespace ohos_rive {
// 线程安全函数回调
void Renderer::TSFNCallback(napi_env env, napi_value js_callback, void *context, void *data)
{
    if (env == nullptr || data == nullptr) {
        return;
    }

    auto *tsfnData = static_cast<TSFNData *>(data);
    Renderer *renderer = static_cast<Renderer *>(context);
    auto timestamp_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(tsfnData->frameTime);
    auto value_ms = timestamp_ms.time_since_epoch().count();

    if (!renderer || renderer->m_isDisposeScheduled) {
        delete tsfnData;
        return;
    }

    napi_handle_scope scope;
    napi_open_handle_scope(env, &scope);

    switch (tsfnData->type) {
        case TSFNData::Type::ADVANCE: {
            // advance
            napi_value advanceMethod;
            if (napi_get_named_property(env, js_callback, "advance", &advanceMethod) == napi_ok) {
                napi_value elapsedMsValue;
                napi_create_double(env, tsfnData->elapsedMs, &elapsedMsValue);
                napi_value argv[] = {elapsedMsValue};
                napi_status status = napi_call_function(env, js_callback, advanceMethod, 1, argv, nullptr);
                if (status != napi_ok) {
                    LOGE("TSFNCallback napi_call_function ADVANCE failed");
                }
            } else {
                LOGE("TSFNCallback napi_get_named_property ADVANCE failed");
            }
            break;
        }
        case TSFNData::Type::DRAW: {
            // draw
            napi_value drawMethod;
            if (napi_get_named_property(env, js_callback, "draw", &drawMethod) == napi_ok) {
                napi_status status = napi_call_function(env, js_callback, drawMethod, 0, nullptr, nullptr);
                if (status != napi_ok) {
                    LOGE("TSFNCallback napi_call_function DRAW failed");
                }
            } else {
                LOGE("TSFNCallback napi_get_named_property DRAW failed");
            }
            break;
        }
        case TSFNData::Type::DISPOSE: {
            napi_value method;
            napi_get_named_property(env, js_callback, "disposeDependencies", &method);
            napi_call_function(env, js_callback, method, 0, nullptr, nullptr);
            break;
        }
    }

    napi_close_handle_scope(env, scope);
    if (tsfnData->completionCallback) {
        tsfnData->completionCallback();
    }
    delete tsfnData;
}

Renderer::Renderer(napi_env env, napi_value ohosRenderer, bool trace, const RendererType rendererType)
    : m_worker(RefWorker::CurrentOrFallback(rendererType)),
      m_renderer(ohosRenderer),
      m_env(env),
      m_tracer(MakeTracer(trace))
{
    // Create a persistent reference to the renderer object
    if (env != nullptr && ohosRenderer != nullptr) {
        napi_create_reference(env, ohosRenderer, 1, &m_rendererRef);
    }
}

Renderer::~Renderer()
{
    if (m_tsfn != nullptr) {
        napi_release_threadsafe_function(m_tsfn, napi_tsfn_abort);
        m_tsfn = nullptr;
    }
    if (!m_isDisposeScheduled) {
        LOGE("Renderer was deleted directly instead of scheduling disposal!");
    }
}

void Renderer::ScheduleDispose()
{
    bool isDisposed = m_isDisposeScheduled.exchange(true);
    if (isDisposed) {
        return;
    }

    m_worker->run([this](DrawableThreadState *threadState) {
        if (m_workerImpl) {
            m_workerImpl->destroy(threadState);
        }

        // dispose
        if (m_tsfn != nullptr) {
            auto *data = new TSFNData{TSFNData::Type::DISPOSE};
            napi_call_threadsafe_function(m_tsfn, data, napi_tsfn_blocking);
        }

        if (m_rendererRef != nullptr && m_env != nullptr) {
            napi_delete_reference(m_env, m_rendererRef);
            m_rendererRef = nullptr;
        }

        ReleaseSurface(m_surface);
        delete this;
    });
}

void Renderer::SetSurface(SurfaceVariant &surface)
{
    if (m_isDisposeScheduled) {
        return;
    }

    auto oldSurface = m_surface;
    AcquireSurface(surface);

    m_worker->run([this, oldSurface](DrawableThreadState *threadState) mutable {
        m_workerThreadID = std::this_thread::get_id();
        if (m_workerImpl) {
            m_workerImpl->destroy(threadState);
            m_workerImpl.reset();
            ReleaseSurface(oldSurface);
        }

        if (m_surface.index() > 0) {
            m_workerImpl = WorkerImpl::Make(m_surface, threadState, m_worker->rendererType());
        }
    });
}

rive::Renderer *Renderer::GetRendererOnWorkerThread() const
{
    if (!m_workerImpl) {
        return nullptr;
    }
    return m_workerImpl->renderer();
}

void Renderer::Start()
{
    if (m_isDisposeScheduled) {
        return;
    }
    napi_value ohosRenderer;
    napi_status tmpStatus = napi_get_reference_value(m_env, m_rendererRef, &ohosRenderer);
    if (tmpStatus != napi_ok) {
        LOGE("Failed to create reference for renderer");
        return;
    }

    napi_value resource_name;
    napi_create_string_utf8(m_env, "RendererTSFN", NAPI_AUTO_LENGTH, &resource_name);

    napi_status status = napi_create_threadsafe_function(m_env, ohosRenderer, nullptr, resource_name,
                                                         0, // Initial thread count
                                                         1, // Max thread count
                                                         nullptr,
                                                         nullptr, // finalizer
                                                         this, TSFNCallback, &m_tsfn);

    if (status != napi_ok) {
        m_tsfn = nullptr;
    }

    m_worker->run([this](DrawableThreadState *threadState) {
        if (!m_workerImpl) {
            return;
        }

        auto now = std::chrono::steady_clock::now();
        m_fpsLastFrameTime = now;
        m_workerImpl->start(now);
    });
}

void Renderer::Stop()
{
    if (m_isDisposeScheduled) {
        return;
    }

    m_worker->run([this](DrawableThreadState *threadState) {
        if (!m_workerImpl) {
            return;
        }
        m_workerImpl->stop();
    });
}

void Renderer::DoFrame()
{
    if (m_isDisposeScheduled) {
        return;
    }

    if (m_numScheduledFrames >= kMaxScheduledFrames) {
        return;
    }

    m_worker->run([this](DrawableThreadState *threadState) {
        if (!m_workerImpl) {
            return;
        }

        auto now = std::chrono::high_resolution_clock::now();
        m_workerImpl->doFrame(m_tracer.get(), threadState, now, m_tsfn);
        m_numScheduledFrames--;
        CalculateFps(now);
    });

    m_numScheduledFrames++;
}

std::unique_ptr<ITracer> Renderer::MakeTracer(bool trace)
{
    if (!trace) {
        return std::make_unique<NoopTracer>();
    } else {
        return std::make_unique<Tracer>();
    }
}

void Renderer::CalculateFps(std::chrono::high_resolution_clock::time_point frameTime)
{
    m_tracer->beginSection("CalculateFps()");
    static constexpr int FPS_SAMPLES = 10;
    double elapsed = std::chrono::duration<double>(frameTime - m_fpsLastFrameTime).count();
    m_fpsSum += 1.0f / elapsed;
    m_fpsCount++;
    if (m_fpsCount == FPS_SAMPLES) {
        m_averageFps = m_fpsSum / static_cast<double>(FPS_SAMPLES);
        m_fpsSum = 0;
        m_fpsCount = 0;
    }
    m_fpsLastFrameTime = frameTime;
    m_tracer->endSection();
}

void Renderer::AcquireSurface(SurfaceVariant &surface)
{
    if (auto *window = std::get_if<EGLNativeWindowType>(&surface)) {
        OH_NativeWindow_NativeObjectReference(reinterpret_cast<void *>(*window));
        m_surface = *window;
    } else {
        m_surface = surface;
    }
}

void Renderer::ReleaseSurface(SurfaceVariant &surface)
{
    if (auto *window = std::get_if<EGLNativeWindowType>(&surface)) {
        OH_NativeWindow_NativeObjectUnreference(reinterpret_cast<void *>(*window));
    } else if (auto *ktSurface = std::get_if<napi_ref>(&surface)) {
        LOGE("Release AcquireSurface: other surface type");
        napi_delete_reference(m_env, *ktSurface);
    }
}

int Renderer::GetWidth() const
{
    if (GetRendererType() == RendererType::Canvas) {
        if (m_workerThreadID == std::thread::id{} || !m_workerImpl) {
            return INVALID_DIMENSION;
        }

        auto renderer = static_cast<CanvasRenderer *>(GetRendererOnWorkerThread());
        return renderer ? renderer->width() : INVALID_DIMENSION;
    } else if (auto window = std::get_if<EGLNativeWindowType>(&m_surface)) {
        int width = 0;
        int height = 0;
        OHNativeWindow *nativeWindow = reinterpret_cast<OHNativeWindow *>(*window);
        OH_NativeWindow_NativeWindowHandleOpt(nativeWindow, GET_BUFFER_GEOMETRY, &height, &width);
        return width;
    }

    return INVALID_DIMENSION;
}

int Renderer::GetHeight() const
{
    if (GetRendererType() == RendererType::Canvas) {
        if (m_workerThreadID == std::thread::id{} || !m_workerImpl) {
            return INVALID_DIMENSION;
        }

        auto renderer = static_cast<CanvasRenderer *>(GetRendererOnWorkerThread());
        return renderer ? renderer->height() : INVALID_DIMENSION;
    } else if (auto window = std::get_if<EGLNativeWindowType>(&m_surface)) {
        int width = 0;
        int height = 0;
        OHNativeWindow *nativeWindow = reinterpret_cast<OHNativeWindow *>(*window);
        OH_NativeWindow_NativeWindowHandleOpt(nativeWindow, GET_BUFFER_GEOMETRY, &height, &width);
        return height;
    }
    return INVALID_DIMENSION;
}

bool Renderer::CallRendererMethod(const char *methodName)
{
    if (m_env == nullptr || methodName == nullptr || m_renderer == nullptr) {
        return false;
    }

    napi_value method;
    if (napi_get_named_property(m_env, m_renderer, methodName, &method) != napi_ok) {
        return false;
    }

    napi_valuetype type;
    napi_typeof(m_env, method, &type);
    if (type != napi_function) {
        return false;
    }

    napi_value result;
    if (napi_call_function(m_env, m_renderer, method, 0, nullptr, &result) != napi_ok) {
        return false;
    }

    return true;
}
} // namespace ohos_rive